#include "luster_subproc.hh"

#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include "luster_flow.hh"
#include "luster_log.hh"
#include "luster_ownership.hh"
#include "luster_status.hh"
#include "luster_string.hh"
#include "luster_test.hh"
#include "luster_vec.hh"

namespace luster {

static void run_child(Cmd *cmd) {
	char const *file = stringbuf_get(&cmd->args[0]);

	Vec<char *> argv;
	for_range(0, cmd->args.len) {
		*vec_append_zero(&argv) = stringbuf_mut(&cmd->args[it]);
	}
	*vec_append_zero(&argv) = 0;

	execvp(file, argv.data);

	logfatal("execvp: %s", strerror(errno));
}

struct ChildPosix {
	pid_t pid;
};

static void cmd_to_string(Cmd const *cmd, StringBuffer *sb) {
	for_range(0, cmd->args.len) {
		if (it != 0) {
			stringbuf_append_char(sb, ' ');
		}

		stringbuf_append_stringbuf(sb, cmd->args[it]);

		if (stringbuf_len(sb) >= 70) {
			stringbuf_append_cstring(sb, " ...");
			break;
		}
	}
}

Child cmd_spawn(Cmd *cmd) {
	Child child;

	ChildPosix *child_ptr = (ChildPosix *)&child;

	StringBuffer cmd_display;
	luster_OWN(cmd_display);

	cmd_to_string(cmd, &cmd_display);

	LOG(INFO) << "$ " << cmd_display << LOGEND;

	pid_t pid = fork();

	if (pid < 0) {
		LOG(FATAL) << "failed to fork" << strerror(errno) << LOGEND;
	}

	if (pid == 0) {
		run_child(cmd);
		LOG(FATAL) << "unreachable: run_child should not return";
	}

	child_ptr->pid = pid;

	LOG(DEBUG) << "spawn: " << pid;

	cmd_clear(cmd);

	return child;
}

Status child_wait(Child *child_wrapper) {
	ChildPosix *child = (ChildPosix *)child_wrapper;

	logprint(LOG_DEBUG, "wait: %d", child->pid);

	int status;
	if (waitpid(child->pid, &status, 0) < 0) {
		logprint(LOG_ERROR, "wait: %s", strerror(errno));
		return STATUS_ERROR;
	}

	if (status != 0) {
		logprint(LOG_ERROR, "subproc exit with non-zero status: %d",
			status);
		return STATUS_ERROR;
	}

	return STATUS_OK;
}

Status cmd_run(Cmd *cmd) {
	Child c = cmd_spawn(cmd);

	luster_TRY(child_wait(&c));

	return STATUS_OK;
}

//
// Pool
//

void child_pool_init(ChildPool *cp, uintptr_t maxproc) {
	if (maxproc == 0) {
		maxproc = 8;
	}
	vec_resize(&cp->ring, maxproc);
}

Status wait_pop(ChildPool *cp) {
	Child c = cp->ring[cp->iget % cp->ring.len];
	luster_TRY(child_wait(&c));
	cp->iget += 1;
	return STATUS_OK;
}

static uintptr_t pool_size(ChildPool const *p) {
	return p->iput - p->iget;
}

Status child_pool_spawn(ChildPool *cp, Cmd *cmd) {
	if (pool_size(cp) >= cp->ring.len) {
		luster_TRY(wait_pop(cp));
	}

	cp->ring[cp->iput % cp->ring.len] = cmd_spawn(cmd);
	cp->iput += 1;
	return STATUS_OK;
}

Status child_pool_wait(ChildPool *cp) {
	while (pool_size(cp) > 0) {
		// logprint(LOG_INFO, "iget, iput := %d, %d", cp->iget,
		// cp->iput);
		luster_TRY(wait_pop(cp));
	}
	return STATUS_OK;
}

luster_TEST(Cmd) {
	Cmd cmd;

	cmd_append(&cmd, "mkdir");
	cmd_append(&cmd, "-p");
	cmd_append(&cmd, "build/tmp/tmp.d");

	cmd_run(&cmd);
}

} // namespace luster
